W9_[ BE101 ] PHP 與 MySQL 實作練習 + w9 直播檢討


Posted by Christy on 2021-07-09

本篇筆記使用 XAMPP 來學習後端,會學到的有:

  1. PHP 基本語法
  2. PHP 背後運作原理簡單介紹
  3. phpmyadmin 資料庫介面管理 MySQL
  4. MySQL 語法基礎
  5. 實作練習:用 PHP 操作 MySQL 裡的資料
    5.1 初探 PHP: PHP 裡有哪些函式?
    5.2 PHP 連線到 MySQL 資料庫
    5.3 讀取資料:Read
    5.4 新增資料:Create / 包含動態新增
    5.5 刪除資料:Delete 待續...
    5.6 編輯資料:Update 待續...

  6. 真正的實戰:留言板 - 初階實作篇
    6.1 串接資料庫顯示留言
    6.2 新增留言功能
    6.3 實作註冊功能
    6.4 實作登入功能
    6.5 如何讓瀏覽器用 COOKIE 記錄登入狀態
    6.6 自己實作通行證機制
    6.7 PHP 內建 session 機制

備註:這是 Lidemy 線上課程 [ BE101 ] 的筆記

1. PHP 基本語法

  • 一定要用標籤 <?php?> 包住,看下面範例
  • 結尾都要加分號
  • 變數前面要加 $
  • 要印出 arr 或資料型態不是單純數字加字串時,用 var_dump or print_r
  • 字串拼接是用 .
<?php
  $a = 'foo'; // 宣告變數 a = foo
  $b = 2; 
  echo $a; // 印出 變數 a
  echo $a . $b; // 印出 a + b -> foo2

  // if...else... 語法
  $score = 60;
  if ($score >= 60) {
    echo 'pass';
  } else {
    echo 'fail';
  }

  // for loop
  for ($i = 0; $i <= 10; $i++) {
    echo $i;
    echo $i . '<br>';
  }

  echo $i . '<br>'; // 會印出
  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10

  $arr = array(1, 2, 3, 4, 5);

  echo $arr[0];
  // 會印出 1;特別注意括號用法

  echo $arr[sizeof($arr) - 1];
  // 會印出 5
  // sizeof() 取得 arr 長度

  echo $arr; // 印不出 arr 內容

  var_dump($arr) // 比較詳細,會印出
  array(5) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) [4]=> int(5)

  print_r($arr) // 不會有 type,會印出
  Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )

  function add($a, $b) {
    return $a + $b;
  }

  echo add(1, 2); // 會印出 3
?>

2. Apache 與 PHP 原理簡介

  • 背後流程:
    • request -> apache(server) -> php -> output -> apache -> response

資料庫系統簡介

  • 什麼是資料庫?
    • 專門來處理資料的一個程式
  • 關聯式資料庫:MySQL, PostgreSQL 這兩個最有名
    • 如果用 XAMPP 的話,裡面用的不是 MySQL 而是 MariaDB,當初 MySQL 被買走了,怕以後不開源,所以 MariaDB 是永遠開源,為了跟 MySQL 完全相容。
    • 但是 MySQL 跟 MariaDB 使用上差不多
  • 非關聯式資料庫:例如 MongoDB

3. 如何管理資料庫? phpmyadmin 簡介

  • phpmyadmin: 一個管理資料庫的介面

    • 之前有過安全性的問題,不過功能完整
    • 或者使用 Adminer 也是用 php 寫的,介面比較簡單一點
  • Index、Unique 這些有什麼用?

    • Index 索引
      • 之後要查詢資料比較快
      • 像是目錄或標籤一樣
      • 可以是組合的欄位,例如 username + password
    • Unique
      • Primary Key (PK 又稱主鍵)不能是空的且不能重複
      • 或也可以設定 unique,可以防止寫入重複的資料

4. MySQL 語法基礎

  • select 查詢資料: SELECT id FROM data

    • SELECT id as name FROM data
      • 改變欄位名稱叫做 name,但還是一樣是 id 內容
    • SELECT id FROM data WHERE id = 2
    • SELECT * FROM data WHERE username = 'yoyoyo' and id = 1,data 有沒有反引號都可以
  • insert 新增資料:

    • sql 指令永遠都大寫
    • 查詢的資料可用反引號包起來或不包,但如果你的名稱是跟 sql 內建指令一樣的話要包,不然會搞混
INSERT INTO `data`(`id`, `username`, `content`, `created_at`) VALUES ('[value-1]','[value-2]','[value-3]','[value-4]')
  • update 修改資料:
UPDATE `data` SET `id`='[value-1]',`username`='[value-2]',`content`='[value-3]',`created_at`='[value-4]' WHERE 1
  • UPDATE data SET username = qoo WHERE id = 1; 如果沒有 WHERE 後面的指定選項的話,就會更改所有的資料

  • delete 刪除資料:

    DELETE FROM data WHERE id = 1

  • 補充說明:有時候會設定 is_deleted 的標籤(選boolean,只有 0 or 1)

    • 如果前台選刪除,那就改成 1,這樣如果誤刪的話,資料可以救回來
    • 如果真的用 delete 且沒有備份,那就救不回來了
    • 或者拿來決定是否要呈現在使用者面前

5. 實作練習:用 PHP 操作 MySQL 裡的資料

5.1 初探 PHP
  • 從前端傳資料給後端:GET 與 POST

    • 這個影片要好好複習,這裡的語法都要會
    • 主要有兩大部分:
      • 利用網址上的 query string 拿資料
      • 透過表單的 method 及 action
        • 通常比較常用的有 GET 和 POST
  • 影片裡的函式有:

    • exit(): 類似 js 裡面 return 的概念
    • isset():
      • isset($_GET['name']) 如果有填寫 name 的內容
    • empty(): 如果內容為空的話
  • 0 - 4'30": 印出網址上 query string 的資料

// 例如網址是:http://localhost:8080/christy/test.php?a=1  這裡的 a 是 key, 1 是 value

執行 print_r($_GET)
會印出 Array ( [a] => 1 )

// 或者是 http://localhost:8080/christy/test.php?a=1&b=3

echo 'a:' . $_GET['a'] . '<br>';
echo 'b:' . $_GET['b'] . '<br>';

會印出
a:1
b:5

// 如果網址有 a 的值,就把 a 印出來
if (isset($_GET['a'])) {
  echo 'a:' . $_GET['a'] . '<br>';
}

會印出 a:1
  • 4'33" - 9'33": 用表單的方式傳資料到後端(GET)
// 用網址傳參數的方式叫做 GET,跟 http 有關
// action 把表單的內容傳到 data.php 去

<form method="GET" action="data.php">
  a: <input name="a" />
  age: <input name="age" />
  <input type="submit" />
</form>

// 這裡的 form 不用包在 <?php?> 裡面喔
  • 另一種寫法
<?php

  if (!isset($_GET['name']) || !isset($_GET['age'])) {
        echo '資料有缺,請再次填寫<br>';
        exit();
    }

    echo 'Hello!' . $_GET['name'] . '<br>';
    echo 'Your age is' . $_GET['age'] . '<br>';
?>

<?php

  if (empty($_GET['name']) || empty($_GET['age'])) {
        echo '資料有缺,請再次填寫<br>';
        exit();
    }

    echo 'Hello! ' . $_GET['name'] . '<br>';
    echo 'Your age is ' . $_GET['age'] . '<br>';
?>
  • 9'40" - 最後:用 POST 交換資料:
<form method="POST" action="data.php">
  a: <input name="a" />
  age: <input name="age" />
  <input type="submit" />
</form>
  • 從 PHP 連線到 MySQL 資料庫

    • 千萬不要把連線設定檔放上 GitHub,一定要把他排除在版本控制裡面,不然會有帳號密碼資料外洩的問題
// 如何讓 MySQL 跟 PHP 做連線

<?php
$server_name = 'localhost';
$username = '****';
$password = '***';
$db_name = 'christy';

$conn = new mysqli($server_name, $username, $password, $db_name);

// die(): 印出括號內的東西以後,停止執行
if ($conn->connect_error) {
    die('資料庫連線錯誤:' . $conn->connect_error);
}

// 用 UTF8 中文就不會是亂碼
// 把時區設為台灣
$conn->query('SET NAMES UTF8');
$conn->query('SET time_zone = "+8:00"');
?>
  • 通常會把連線的部分分開寫,建立一個 conn.php 檔案,裡面只有連線相關資訊(就上面那幾行程式碼)

  • 在其他檔案裡第一行寫,就可以連接了 require_once('conn.php');

5.2 讀取資料:Read
  • PHP 與 MySQL 互動
    • 讀取資料 「讀取 users 資料庫裡面所有的資料」
      • 'select now() from users;' 選取現在時間(MySQL 內建函式)
      • 大方向就是:引入連線 -> query 資料庫 -> 處理錯誤 -> fetch 結果 -> 拿到想要的內容
<?php
    require_once('conn.php');

    $result = $conn->query('SELECT * from users;');

    if (!$result) {
        die($conn->error);
    }

    $row = $result->fetch_assoc();
    print_r($row);

    while ($row = $result->fetch_assoc()) {
        print_r($row);
    }   
?>
  • 詳細解釋
<?php
// 引入連線
require_once('conn.php');

// 設一個變數來裝結果,用 $conn->query('這裡放 sql 語法') 執行
  $result = $conn->query('select * from users;');

  // 如果沒有結果,就把錯誤印出來並結束程式
  if (!$result) {
      die($conn->error);
  }

  // fetch 拿資料,每執行一次就有一筆資料
  // 這樣寫就會有兩筆資料

  $row = $result->fetch_assoc();
  print_r($row['id']);
  print_r($row['username']);

  $row = $result->fetch_assoc();
  print_r($row['id']);
  print_r($row['username']);

  // 可以用 while 迴圈把資料都拿出來
  // 會印出 Array ( [id] => 1 [username] => Peter ) Array ( [id] => 2 [username] => Nick ) Array ( [id] => 3 [username] => Diana )

  while ($row = $result->fetch_assoc()) {
    print_r($row);
  }

  // 會印出
  // 1 Peter
  // 2 Nick
  // 3 Diana

  while ($row = $result->fetch_assoc()) {
    print_r($row['id'] . ' ' . $row['username'] . '<br>');
  }
?>
5.3 新增資料:Create / 包含動態新增
  • 大方向一樣是: 建立連線 -> query 資料庫,使用新增資料的 sql 語法 -> 處理錯誤 -> 拿到想要的資料

  • sql 原本的語法:「在 users 這個資料庫裡,新增 username 叫 'apple' 的資料」

<?php
    require_once('conn.php');

    // 實例長這樣
    // 但是寫這樣資料一多很容易搞混   
        $result = $conn->query("INSERT INTO users(username) VALUES('apple');");

    if (!$result) {
        die($conn->error);
    }

  print_r($result); // 會印出 1,代表有新增成功
?>
  • 也可以這樣寫,但是非常難懂,不推薦
<?php
    require_once('conn.php');

    $username = 'apple';

    // 也可以寫成拼接式,但很難一眼看懂
    $sql = "INSERT INTO users(username) VALUES ('" .  $username ."')";

    echo $sql;

    $result = $conn->query($sql);

    if (!$result) {
        die($conn->error);
    }

  print_r($result);
?>
  • 最推薦的方法:用 sprintf() 來實現「在 users 這個資料庫裡,新增 username 叫 'apple' 的資料」

    • 基本架構:建立連線 -> 設變數 $username = 想要的資料 -> 設變數sql = sprintf("sql 新增語法", $username) -> query 資料庫 -> 處理錯誤 -> 拿到想要的資料
<?php
    require_once('conn.php');

    $username = 'apple';

    $sql = sprintf(
        "INSERT INTO users(username) VALUES('%s')",
        $username
    );

    $result = $conn->query($sql);

    if (!$result) {
        die($conn->error);
    }

    print_r($result);
?>
  • 詳細解釋
<?php
    require_once('conn.php');

    $username = 'apple';

    // 用 sprintf()這個函式來新增資料
    // %d is used for numbers (integers)
    // %s is used for letters (strings)
    // 字串要包起來
    // 會按造順序新增,id -> 13, username -> $username

    $sql = sprintf(
        "INSERT INTO users(id, username) VALUES (%d, '%s')",
        13,
        $username
    );

    echo $sql;

    $result = $conn->query($sql);

    if (!$result) {
        die($conn->error);
    }

  print_r($result);
?>
  • 「利用 POST 的方式動態新增 username」
<?php
    require_once('conn.php');

    if (empty($_POST['username'])) {
        die('請輸入 username');
    }

    $username = $_POST['username'];

    $sql = sprintf(
        "INSERT INTO users(username) VALUES('%s')",
        $username
    );

    $result = $conn->query($sql);

    if (!$result) {
        die($conn->error);
    }
    // 跳轉回原本頁面
    header('Location: test.php');
?>

6. 真正的實戰:留言板 - 初階實作篇

  • 在實作前:
    • 要先想好有哪些資料內容
    • 建置資料庫
    • 切版

6.1 串接資料庫顯示留言
6.2 新增留言功能
6.3 實作註冊功能
6.4 實作登入功能

6.1 串接資料庫顯示留言
  • 把 PHP 寫在 html 最上面
  • PHP 程式碼可以放在任何地方
<?php
    require_once('conn.php');

    $result = $conn->query('SELECT * from comments ORDER BY id DESC');
    if (!$result) {
        die('error:' . $conn->connect_error);
    }

    // 下面這一段放在 html 的標籤裡面
    while  ($row = $result->fetch_assoc()) {
        print_r($row);
    }
?>
  • 在 html 裡面長這樣
<section>
  <?php
    while ($row = $result->fetch_assoc()) { 
  ?>
    <div class="comment">
      <div class="comment__avatar">
    </div>

    <div class="comment__info">
      <div class="comment__data">
        <div class="comment__nickname">
          <?php echo $row['nickname']; ?>
        </div>
        <div class="comment__time">
          <?php echo $row['created_at']; ?>
        </div>
      </div>

    // 這裡縮成一行,留言就不會有空白的問題
    // 這兩個 css 屬性很常用
    // word-wrap: break-word;
    // white-space: pre-line;
    <div class="comment__content"><?php echo $row['content']; ?></div>
    </div>
  </div>
<?php } ?>      
</section>
6.2 新增留言功能
  • 其實就是動態新增的應用啦
  • 要注意幾點:
    • 在 form 標籤裡面 action 要寫檔案名稱 <form class="bulletin__post" method="POST" action="handle_new_post.php"></form>
    • 留言區塊的 name 要取跟資料庫一樣 <textarea name="content" rows="10"></textarea>
// handle_new_post.php 檔案內容
<?php
    require_once('conn.php');

    if (
        empty($_POST['nickname']) || 
        empty($_POST['content'])
    ) {
        die('請填寫資料');
    }

    $nickname = $_POST['nickname'];
    $content = $_POST['content'];

    $sql = sprintf(
        "INSERT INTO comments(nickname, content) VALUES('%s', '%s')", 
        $nickname, $content
    );

    $result = $conn->query($sql);

    if (!$result) {
        die($conn->error);
    }

    header('Location: index.php');
?>
  • 如果沒有暱稱、留言內容的情況
    • 先把錯誤訊息用 GET 導到另一個網址
    • 在 html 裡面放上 php 的程式碼
// handle_new_post.php 檔案內容

<?php
    require_once('conn.php');

    if (empty($_POST['nickname']) || empty($_POST['content'])) {

        // 先把頁面用 GET 的方式導到下面網址
        header('Location: index.php?errMsg=資料不齊全');
        die('請填寫資料');
    }

    $nickname = $_POST['nickname'];
    $content = $_POST['content'];

    $sql = sprintf("INSERT INTO comments (nickname, content) VALUES ('%s', '%s')", $nickname, $content);

    $result = $conn->query($sql);

    if (!$result) {
        die($conn->error);
    }

    header('Location: index.php');
?>
// 這樣寫的缺點就是網址後面隨便改個內容,網頁顯示的提示就會被取代
<?php
    if (!empty($_GET['errMsg'])) {
        $msg = $_GET['errMsg'];
        echo '<h2>' . $msg . '<h2>';
    }
?>
  • 比較好的做法(但真正好的做法是在前端用 js 監聽按鈕的方式先處理)
// 在 handle_new_post.php 檔案內容把頁面導到下面網址
if (empty($_POST['nickname']) || empty($_POST['content'])) {
    header('Location: index.php?errCode=1');
    die('請填寫資料');
}

// 在 html 加上這段程式碼
<?php
    if (!empty($_GET['errCode'])) {
        $code = $_GET['errCode'];

        // 不懂為什麼要放下面這行,因為不寫也可以運作
        $msg = 'Error';
        if ($code === '1') {
            $msg = '資料不齊全';
        }
        echo '<h2 class="error">' . $msg . '</h2>';
    }
?>
6.3 實作註冊功能
  • 頁面有:register.php, handle_register.php
  • 把註冊頁面切出來
  • 註冊功能邏輯:
    • 先引入連線 -> 新增註冊的資料(暱稱、使用者名稱、密碼)-> 最後導回首頁
  • 要注意的地方:
    • 發生錯誤的情形有兩種:
      • 帳號已被註冊(資料庫要把 username 選成唯一)
      • 請填寫資料(檢查所有欄位是否為空)
// handle_register.php 檔案內容
<?php
    require_once('conn.php');

    if (empty($_POST['nickname']) || empty($_POST['username']) || empty($_POST['password'])) {
        // 檢查欄位是否為空
        header('Location: register.php?errCode=1');
        die();
    }

    $nickname = $_POST['nickname'];
    $username = $_POST['username'];
    $password = $_POST['password'];

    $sql = sprintf("INSERT INTO users (nickname, username, password) VALUES ('%s', '%s', '%s')", $nickname, $username, $password);
    $result = $conn->query($sql);

    if (!$result) {
        // errno 是經過 mysql 定義的
        // 1062 不用包起來
        $code = $conn->errno;
        if ($code === 1062) {
            header('Location: register.php?errCode=2');
        }
        die($conn->error);
    }

    header('Location: index.php');
?>
6.4 實作登入功能
  • 頁面有:login.php, handle_login.php
  • 把登入頁面切出來
  • 登入功能邏輯:
    • 先引入連線 -> 判斷資料庫裡面是否有一樣的使用者跟密碼 -> 處理錯誤(資料不齊全、查無帳號或密碼)-> 登入成功,導回首頁
  • 要注意的地方:
    • 登入的邏輯是 $sql = sprintf("SELECT * FROM users WHERE username = '%s' and password = '%s'", $username, $password);
    • 如何判斷登入有誤的情形?
      • 利用 echo $result->num_rows;,會輸出資料庫裡面有幾筆資料,輸出為零代表零筆資料,表示登入帳號或密碼有誤
      • 輸出為一,代表有一筆資料,表示有找到帳號密碼
// handle_login.php 內容

<?php
    require_once('conn.php');

    if (empty($_POST['username']) || empty($_POST['password'])) {
        header('Location: login.php?errCode=1');
        die();
    }
    // 登入時輸入的帳密,也是使用 $_POST 的方式
    $username = $_POST['username'];
    $password = $_POST['password'];

    $sql = sprintf("SELECT * FROM users WHERE username = '%s' and password = '%s'", $username, $password);

    $result = $conn->query($sql);
    if (!$result) {
        die($conn->error);
    }
    // num_rows 是 sql 內建的語法,判斷結果有多少筆資料
    // 當結果為 1,就是有一筆資料
    if ($result->num_rows) {
        echo '登入成功!';
        header('Location: index.php');
    } else {
        header('Location: login.php?errCode=2');
    }
?>
6.5 如何讓瀏覽器用 COOKIE 記錄登入狀態
<?php
$cookie_name = "user";
$cookie_value = "John Doe";
setcookie($cookie_name, $cookie_value, time() + (86400 * 30), "/"); // 86400 = 1 day
?>
// handle_login.php 內容
<?php
    require_once('conn.php');

    if (empty($_POST['username']) || empty($_POST['password'])) {
        header('Location: login.php?errCode=1');
        die();
    }

    $username = $_POST['username'];
    $password = $_POST['password'];

    $sql = sprintf("SELECT * FROM users WHERE username = '%s' and password = '%s'", $username, $password);

    $result = $conn->query($sql);
    if (!$result) {
        die($conn->error);
    }

    if ($result->num_rows) {
        echo '登入成功!';
        // 主要寫下面三行
        $expire = time() + 3600 * 24 * 30;
        setcookie('username', $username, $expire);
        header('Location: index.php');
    } else {
        header('Location: login.php?errCode=2');
    }
?>
  • 在首頁拿到登入的狀態及資訊:
    • 可以先把 Cookie 印出來觀察,會發現是一個 array
    • print_r($_COOKIE);
    • 接著到index.php 的內容,執行下面的程式碼
$username = NULL;
if (!empty($_COOKIE['username'])) {
    $username = $_COOKIE['username'];
}
  • 登出功能:
    • 讓 cookie 過期,把 username 清空
// logout.php 檔案內容
<?php
    setcookie('username', '', time() - 3600);
    header('Location: index.php');
?>
  • 使用 cookie 紀錄 username 登入情形,去資料庫裡面找到相對應的暱稱
  • 關鍵是這兩行
  • 把使用者存在 cookie 裡面的風險就是可以隨意偽造身份
$username = $_COOKIE['username'];
    $user_sql = sprintf("SELECT nickname FROM users WHERE username = '%s'", $username);
// handle_new-post.php 內容
<?php
    require_once('conn.php');

    if (empty($_POST['content'])) {
        header('Location: index.php?errCode=1');
        die('請填寫資料');
    }
    // 使用 cookie 紀錄 username 登入情形,去資料庫裡面找到 username 的 暱稱
    $username = $_COOKIE['username'];
    $user_sql = sprintf("SELECT nickname FROM users WHERE username = '%s'", $username);

    $user_result = $conn->query($user_sql);
    $row = $user_result->fetch_assoc();
    $nickname = $row['nickname'];

    $content = $_POST['content'];

    $sql = sprintf("INSERT INTO comments (nickname, content) VALUES ('%s', '%s')", $nickname, $content);
    $result = $conn->query($sql);

    if (!$result) {
        die($conn->error);
    }

    header('Location: index.php');
?>
6.6 自己實作通行證機制
  • 實作 token 機制

先跳過,之後再回頭學。

6.7 PHP 內建 session 機制
  • 要用 session 的話,要在加檔案第一行 -> session_start();
  • 只要寫 $_SESSION['username'] = $username;
    • 這樣就把 username 存在 session 裡面了
    • 一行程式碼做三件事
      • 產生 session id (token)
      • 把 username 寫入檔案
      • set-cookie: 把 session-id 設定到 client 端去
    • 登出用 session_destroy();
  • PHP 最高!❤️❤️❤️
7. 做個 reddit 自己玩

7.1 版面建置

  • 資料庫: redditt_users/reddit_comments
    • reddit_users: id/username/password/created_at
      • id: int/不用編碼
      • username: varchar(64)/utf8mb4_general_ci
      • password: varchar(128)/utf8mb4_general_ci
      • created_at: datetime/預設值 current_timestamp()
    • reddit_comments: id/username/content/created_at
      • content: text/utf8mb4_general_ci
  • 犯的錯誤:
    • 登入後跳轉到首頁,發現新增留言時無法一起新增 username,原來是我 handle_new_post.php 功能少寫了抓取 username,這裡要取兩次資料,有點不太習慣語法
    • sig up 之後跳轉到首頁,沒辦法拿到 username,所以我要在 handle_signup.php 設 setcookie()。
    • 解決了使用者名稱太長,在首頁破版的問題,加上兩行就好了
      • word-break: break-all;
      • white-space: pre-line;
    • 把 RWD 做好了,多虧了這兩行
      • width: 100%;
      • min-width: 768px;
  • 沒想到我能夠自己做出一個來,好開心!
8. 作業參考資料

[教學] 什麼是 Cookie?如何用 JS 讀取/修改 document.cookie?

9. W9 直播檢討
  • 還是有點搞不清楚,以註冊登入系統來說,哪些部分是前端需要驗證而哪些又是後端驗證的呢?

    • 前端驗證是為了使用者體驗
    • 後端驗證是為了安全性
  • LIOJ 1053 走迷宮會用到BFS(廣度優先搜尋法),查了一下發現還是看不太懂在寫什麼,老師會建議先去讀演算法的資料,還是等課程在往後學幾週再回來解呢?謝謝

    • 可以課程結束後再回來寫,優先順序沒有這麼高
  • 老師解釋前後端概念










Related Posts

VS Code 中文套件導致 TypeScript 偵錯問題

VS Code 中文套件導致 TypeScript 偵錯問題

Jump Diffusion Option Valuation in  Discrete Time

Jump Diffusion Option Valuation in Discrete Time

COSCUP 2020 貼紙蒐集者

COSCUP 2020 貼紙蒐集者


Comments